/*
 * Copyright 2008 p.langer
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * 
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * 	http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.ac.tuwien.big.easyholdem.game;

import java.util.Collection;

import at.ac.tuwien.big.easyholdem.player.Player;
import at.ac.tuwien.big.easyholdem.poker.Card;
import at.ac.tuwien.big.easyholdem.poker.Cards;
import at.ac.tuwien.big.easyholdem.poker.Deck;
import at.ac.tuwien.big.easyholdem.poker.HandRank.Rank;

/**
 * <p>
 * An easy hold'em game.
 * </p>
 * 
 * <p>
 * Created on 16.01.2008 at 15:38:30.
 * </p>
 * 
 * @author Philip Langer <langer[at]big[dot]tuwien[dot]ac[dot]at>
 */
public class Game {

	/**
	 * The current player.
	 */
	private Player player;

	/**
	 * The current state of this game.
	 */
	private GameState currentState;

	/**
	 * The current fixed bet size.
	 */
	private int fixedBetSize = 1;

	/**
	 * The current pot size.
	 */
	private int potSize = 0;

	/**
	 * The result of this game.
	 */
	private ShowdownResult showdownResult = null;

	/*
	 * The card fields.
	 */
	private Deck deck = new Deck();
	private Cards playersCards = new Cards(2);
	private Cards dealersCards = new Cards(2);
	private Cards flopCards = new Cards(3);
	private Card turnCard = null;
	private Card riverCard = null;
	
	/**
	 * Creates a new Game with a new {@link Player}.
	 */
	public Game() {
		super();
		this.player = new Player();
		restart();
	}

	/**
	 * Creates a new Game with the specified <code>player</code> and the
	 * default fixed bet size.
	 * 
	 * @param player
	 *            the player.
	 */
	public Game(Player player) {
		super();
		this.player = player;
		restart();
	}

	/**
	 * Creates a new Game with the specified <code>player</code> and the
	 * <code>fixedBetSize</code>.
	 * 
	 * @param player
	 *            the player.
	 * @param fixedBetSize
	 *            the fixed bet size.
	 */
	public Game(Player player, int fixedBetSize) {
		super();
		this.player = player;
		this.fixedBetSize = fixedBetSize;
		restart();
	}

	/**
	 * Restarts the game.
	 */
	public void restart() {
		this.restart(this.fixedBetSize);
	}

	/**
	 * Restarts the game with a new specified <code>fixedBetSize</code>.
	 * 
	 * @param fixedBetSize
	 *            fixed bet size.
	 */
	public void restart(int fixedBetSize) {
		// set bet size
		this.fixedBetSize = fixedBetSize;
		
		// reset result and pot size
		this.showdownResult = null;
		this.potSize = 0;

		// reset cards
		this.dealersCards.clear();
		this.flopCards.clear();
		this.playersCards.clear();
		this.riverCard = null;
		this.turnCard = null;
		
		// shuffle deck
		this.deck = new Deck();
		this.deck.shuffle();

		// set new state
		this.currentState = new PreHoleCardsState(this);
	}

	/**
	 * Returns the currently set fixed bet size.
	 * 
	 * @return fixed bet size.
	 */
	public int getFixedBetSize() {
		return this.fixedBetSize;
	}

	/**
	 * Returns the player.
	 * 
	 * @return the player
	 */
	public Player getPlayer() {
		return player;
	}

	/**
	 * Returns the current pot size.
	 * 
	 * @return the pot size
	 */
	public int getPotSize() {
		return potSize;
	}

	/**
	 * Returns the player's hole cards.
	 * 
	 * @return the player's cards
	 */
	public Cards getPlayersCards() {
		return playersCards;
	}

	/**
	 * Returns the flop cards.
	 * 
	 * @return the flop cards
	 */
	public Cards getFlopCards() {
		return flopCards;
	}

	/**
	 * Returns the turn card.
	 * 
	 * @return the turn card
	 */
	public Card getTurnCard() {
		return turnCard;
	}

	/**
	 * Returns the river card.
	 * 
	 * @return the river card
	 */
	public Card getRiverCard() {
		return riverCard;
	}

	/**
	 * Returns the current state of the game.
	 * 
	 * @return the current state.
	 */
	public State getState() {
		return this.currentState.getState();
	}

	/**
	 * Returns the currently possible actions.
	 * 
	 * @return currently possible actions.
	 */
	public Collection<Action> getPossibleActions() {
		return this.currentState.getPossibleActions();
	}

	/**
	 * Executes the specified <code>actionToAct</code>. If the specified
	 * <code>actionToAct</code> is not valid in the current state an
	 * {@link IllegalArgumentException} is thrown.
	 * 
	 * @param actionToAct
	 *            action to execute.
	 * @throws IllegalArgumentException
	 *             if the action is not valid in the current state.
	 */
	public void act(Action actionToAct) {
		this.currentState.act(actionToAct);
	}

	/**
	 * Returns <code>true</code> if the game is over. <code>false</code>
	 * otherwise.
	 * 
	 * @return <code>true</code> if the game is over. <code>false</code>
	 *         otherwise.
	 */
	public boolean isOver() {
		return this.currentState.getState().equals(State.END);
	}

	/**
	 * Returns the result of the showdown. This is only possible in the
	 * {@link State}.END state, that means if the game is over (isOver()).
	 * 
	 * @return result of the showdown.
	 */
	public ShowdownResult getShowdownResult() {
		return this.showdownResult;
	}

	/**
	 * Sets the current game state.
	 * 
	 * @param gameState
	 *            game state to set.
	 */
	protected void setGameState(GameState gameState) {
		this.currentState = gameState;
	}

	/**
	 * Takes the ante away from the current player.
	 */
	protected void playerPaysAnte() {
		this.player.decreaseStack(this.fixedBetSize);
	}

	/**
	 * Takes the <code>amount</code> as bet away from the player.
	 * 
	 * @param amount
	 *            amount to bet.
	 */
	protected void playerBets(int amount) {
		this.player.decreaseStack(amount);
		this.potSize += amount * 2;
	}

	/**
	 * Deals the hole cards to the player and the dealer.
	 */
	protected void dealHoleCards() {
		// deal hole cards
		this.playersCards.add(deck.deal());
		this.dealersCards.add(deck.deal());

		this.playersCards.add(deck.deal());
		this.dealersCards.add(deck.deal());
	}

	/**
	 * Deals the flop cards.
	 */
	protected void dealFlopCards() {
		deck.burn();
		this.flopCards.add(deck.deal());
		this.flopCards.add(deck.deal());
		this.flopCards.add(deck.deal());
	}

	/**
	 * Deals the turn card.
	 */
	protected void dealTurnCard() {
		deck.burn();
		this.turnCard = deck.deal();
	}

	/**
	 * Deals the river card.
	 */
	protected void dealRiverCard() {
		deck.burn();
		this.riverCard = deck.deal();
	}

	/**
	 * Creates a showdown result, which is available through getShowdownResult
	 * afterwards.
	 */
	protected void createShowdownResult() {
		ShowdownResult gr = new ShowdownResult(playersCards, dealersCards,
				flopCards, turnCard, riverCard);
		this.showdownResult = gr;
	}

	/**
	 * Pays the whole pot to the players stack if the player has won. The half
	 * pot if there is a split pot result and the whole pot plus the ante amount
	 * if the player had mor than a three of a kind.
	 */
	protected void payPot() {
		if (this.showdownResult != null) {
			if (this.showdownResult.hasPlayerWon()) {

				this.player.increaseStack(this.potSize);

				// if hand rank is straight or higher pay ante
				if (this.showdownResult.getPlayersHandRank().getRank()
						.compareTo(Rank.THREEOFAKIND) > 0) {
					this.player.increaseStack(this.fixedBetSize * 2);
				}

			} else if (this.showdownResult.isSplitPot()) {
				this.player.increaseStack(this.potSize / 2);
			}
			
			// reset pot
			this.potSize = 0;
		}
	}

}
